/**
 * @fileOverview 
 * Defines the minimum set of geometric primitives that was used in
 * geo-polygon-utils library.
 * @author Igor Kulkin <igor.kulkin@gmail.com>
 */


var geoPolygonUtils = geoPolygonUtils || {};


/**
 * @namespace Contains some basic geometry primitives.
 * @memberOf geoPolygonUtils
 */
geoPolygonUtils.geometry = geoPolygonUtils.geometry || {};


(function() {
  /** @exports Precision as geoPolygonUtils.geometry.Precision */
  var Precision =
    /**
     * Creates precision object initialized with given constants.
     * @param {Number} epsilon
     * @param {Number} unit
     * @param {Number} radian
     * @class
     * <p>Defines constants used as a precision when comparing floating point numbers.</p>
     * <p>These constants are normally used for two purposes:
     * <ul>
     *   <li>comparing two floating point numbers (the values are considered to be equal in
     *   case they differ by less than a value of corresponding precision constant);</li>
     *   <li>comparing numbers with zero (the value is considered to be zero if its absolute
     *   value is smaller than corresponding precision constant).</li></ul></p>
     */
    geoPolygonUtils.geometry.Precision = function(epsilon, unit, radian) {
      /**
       * Generic epsilon value used when neither on of 'UNIT' and 'RADIAN' fits.
       * Use case example: checking if a matrix has non-zero discriminant when intersecting two lines.
       * @type {Number}
       */
      this.EPSILON = epsilon;

      /**
       * Precision constant to compare lengths and coordinates.
       * Here are several examples showing when this constant is being used:
       * <ul>
       *   <li>figuring out if two points are equal;</li>
       *   <li>checking if a vector has zero length;</li>
       *   <li>checking if a line contains a point.</li>
       * </ul>
       *
       * @type {Number}
       */
      this.UNIT = unit;

      /**
       * Precision constant to compare angles (in radians).
       * Here are several examples showing when this constant is being used:
       * <ul>
       *   <li>checking angles equality;</li>
       *   <li>checking equality of two lines (polar angles of their normals are being compared).</li>
       * </ul>
       * @type {Number}
       */
      this.RADIAN = radian;
    };
    
  
  /**
   * Creates a precision object based on DEFAULT precision given an override precision for EPSILON.
   * @param {Precision|Number} [epsilonPrecision] Optional precision override, which may be a
   *     Number override for Precision.EPSILON or the whole Precision object or may even be undefined,
   *     in which case the DEFAULT precision object is returned.
   */
  Precision.createForEpsilon = function(epsilonPrecision) {
    if (!epsilonPrecision) {
      return Precision.DEFAULT;
    }
    if (typeof(epsilonPrecision) != 'number') {
      return epsilonPrecision;
    }
    return new Precision(epsilonPrecision, Precision.DEFAULT.UNIT, Precision.DEFAULT.RADIAN);
  };
    
  
  /**
   * Creates a precision object based on DEFAULT precision given an override precision for UNIT.
   * @param {Precision|Number} [unitPrecision] Optional precision override, which may be a
   *     Number override for Precision.UNIT or the whole Precision object or may even be undefined,
   *     in which case the DEFAULT precision object is returned.
   */
  Precision.createForUnit = function(unitPrecision) {
    if (!unitPrecision) {
      return Precision.DEFAULT;
    }
    if (typeof(unitPrecision) != 'number') {
      return unitPrecision;
    }
    return new Precision(Precision.DEFAULT.EPSILON, unitPrecision, Precision.DEFAULT.RADIAN);
  };
    
  
  /**
   * Creates a precision object based on DEFAULT precision given an override precision for RADIAN.
   * @param {Precision|Number} [radianPrecision] Optional precision override, which may be a
   *     Number override for Precision.RADIAN or the whole Precision object or may even be undefined,
   *     in which case the DEFAULT precision object is returned.
   */
  Precision.createForRadian = function(radianPrecision) {
    if (!radianPrecision) {
      return Precision.DEFAULT;
    }
    if (typeof(radianPrecision) != 'number') {
      return radianPrecision;
    }
    return new Precision(Precision.DEFAULT.EPSILON, Precision.DEFAULT.UNIT, radianPrecision);
  };


  /**
   * Checks if a given value is zero (+/- epsilon).
   * @param {Number} value The number to be tested.
   * @param {Number} epsilon Precision of the test.
   * @returns {Boolean}
   */
  Precision.isZero = function(value, epsilon) {
    return Math.abs(value) < epsilon;
  }


  /**
   * Checks two numbers for equality with a given epsilon.
   * @param {Number} valueA The first value to be compared.
   * @param {Number} valueB The second value to be compared.
   * @returns {Boolean}
   */
  Precision.equal = function(valueA, valueB, epsilon) {
    return Math.abs(valueA - valueB) < epsilon;
  }


  /**
   * Compares two given numbers with a given epsilon.
   * @param {Number} valueA The first value to be compared.
   * @param {Number} valueB The second value to be compared.
   * @returns {Number} Result of comparison. One of {-1, 0, 1}.
   */
  Precision.cmp = function(valueA, valueB, epsilon) {
    if (valueA <= valueB - epsilon) return -1;
    if (valueA < valueB + epsilon) return 0;
    return 1;
  }


  /**
   * <p>Precision object with a set of default precision constants. By default
   * each of the precision thresholds is set to <i>1e-10</i>.</p>
   * <p>These precision constants are used implicitly throughout the library
   * in places where it didn't make sense to allow user to specify her own
   * precision thresholds. They also act as default precision value when user
   * chooses not to specify her own precision thresholds.</p>
   * <p><b>IMPORTANT:</b> <i>override these constants only if you know what you're doing.</i></p>
   */
  Precision.DEFAULT = new Precision(1e-10, 1e-10, 1e-10);


  /** @exports Vector as geoPolygonUtils.geometry.Vector */
  var Vector =
    /**
     * Creates a vector with given coordinates.
     * @param {Number|String} x
     * @param {Number|String} y
     * @class Represents a vector in 2-dimensional space.
     */
    geoPolygonUtils.geometry.Vector = function(x, y) {
      /** @type {Number} */
      this.x = Number(x);
      /** @type {Number} */
      this.y = Number(y);
    };


  /**
   * Creates a vector given a polar angle and length.
   * @param {Number} angle Polar angle.
   * @param {Number} length Vector length.
   * @returns {Vector}
   * @static
   */
  Vector.fromPolar = function(angle, length) {
    return new Vector(length * Math.cos(angle), length * Math.sin(angle));
  };


  /**
   * Multiplies a given vector by a scalar coefficient.
   * The vector itself is not modified, the result is returned as a new Vector instance.
   * @param {Number} coef Scalar coefficient.
   * @returns {Vector} The result of multiplication.
   */
  Vector.prototype.multiply = function(coef) {
    var c = Number(coef);
    return new Vector(c * this.x, c * this.y);
  };


  /**
   * Returns the sum of two vectors.
   * @param {Vector}
   * @returns {Vector}
   */
  Vector.prototype.plus = function(v) {
    return new Vector(this.x + v.x, this.y + v.y);
  };


  /**
   * Returns the result of subtraction of the given vector from 'this' vector.
   * @param {Vector}
   * @returns {Vector}
   */
  Vector.prototype.minus = function(v) {
    return new Vector(this.x - v.x, this.y - v.y);
  };


  /**
   * Calculates dot-product of two vectors.
   * @param {Vector}
   * @returns {Number} Dot product.
   */
  Vector.prototype.dotProduct = function(v) {
    return this.x * v.x + this.y * v.y;
  };


  /**
   * Calculates cross-product of two vectors (which is a scalar for 2-dimensional vectors).
   * @param {Vector}
   * @returns {Number} Cross product.
   */
  Vector.prototype.crossProduct = function(v) {
    return this.x * v.y - this.y * v.x;
  };


  /**
   * Returns a vector orthogonal to 'this' vector.
   * The resulting vector has the same length as the original one.
   * You can assume that the same direction of rotation is applied for different calls of this method.
   * @returns {Vector}
   */
  Vector.prototype.ortho = function() {
    return new Vector(-this.y, this.x);
  };


  /**
   * Checks if 'this' vector has zero length.
   * @param {Number|Precision} [precision] Precision for the check.
   * @return {Boolean}
   */
  Vector.prototype.isZero = function(precision) {
    precision = Precision.createForUnit(precision);
    return Precision.isZero(this.x, precision.UNIT) &&
           Precision.isZero(this.y, precision.UNIT);
  };


  /**
   * Returns polar angle that specifies the direction of 'this' vector.
   * The resulting angle is guaranteed to lie within [0, 2*pi).
   * Note: the function returns 'null' if 'this' vector is of zero length.
   * @returns {Number|null} Polar angle.
   */
  Vector.prototype.angle = function() {
    if (this.isZero()) {
      return null;
    }
    var a = Math.atan2(this.y, this.x);
    if (Precision.isZero(a, Precision.DEFAULT.RADIAN)) {
      return 0.0;
    }
    if (a < 0) {
      a += 2 * Math.PI;
    }
    return a;
  };


  /**
   * Returns 'this' vector's length.
   * @returns {Number} Vector's length.
   */
  Vector.prototype.norm = function() {
    return Math.sqrt(this.norm2());
  };


  /**
   * Square of 'this' vector's length.
   * @return {Number} Square of vector's length.
   */
  Vector.prototype.norm2 = function() {
    return this.x * this.x + this.y * this.y;
  };


  /**
   * Returns 'this' vector normalized (ie of a unit length).
   * Note: in case the vector's length is zero 'null' is returned.
   * @returns {Vector|null} Normalized vector or 'null' in case the vector is
   *     of zero length.
   */
  Vector.prototype.normalized = function() {
    var len = this.norm();
    if (Precision.isZero(len, Precision.DEFAULT.UNIT)) {
      return null;
    }
    return this.multiply(1 / len);
  };


  /**
   * Returns distance from 'this' vector to a given point.
   * @param {Vector} point
   * @returns {Number} Distance to the given point.
   */
  Vector.prototype.distanceToPoint = function(point) {
    return Math.sqrt(Math.pow(point.x - this.x, 2) +
        Math.pow(point.y - this.y, 2));
  };


  /**
   * Determines whether 'this' point is equal to the given point (modulo some precision).
   * @param {Vector} point
   * @param {Number|Precision} [precision] Precision for the check.
   * @returns {Boolean}
   */
  Vector.prototype.equals = function(point, precision) {
    precision = Precision.createForUnit(precision);
    return Precision.equal(this.x, point.x, precision.UNIT) &&
           Precision.equal(this.y, point.y, precision.UNIT);
  };


  /**
   * Alias name for a Vector.
   */
  geoPolygonUtils.geometry.Point = Vector;


  /** @exports Line as geoPolygonUtils.geometry.Line */
  var Line =
    /**
     * <p>Creates a line given its 'normal' and constant parameter 'c'.
     * Line is defined with the following equation:
     *   <i>normal.dotProduct(point) + c = 0</i></p>
     * <p>Note: normal needs to be a non-zero vector. Otherwise the result is
     * undefined.</p>
     * <p>Although you can use this constructor directly, normally you're
     * better of using one of the static factory methods as they provide a more
     * user-friendly interface.</p>
     * @param {Vector} normal
     * @param {Number} c
     * @class Represents a line in 2-dimensional space.
     */
    geoPolygonUtils.geometry.Line = function(normal, c) {
      var scale = 1 / normal.norm();
      /** @type {Vector} */
      this.normal = normal.multiply(scale);
      /** @type {Number} */
      this.c = c * scale;
    };


  /**
   * Constructs a line that has a given normal and goes through a given point.
   * Note: the function may return 'null' in case the given normal is a
   * zero-vector.
   * @param {Vector} normal
   * @param {Vector|Point} point
   * @returns {Line|null}
   */
  Line.fromNormalAndPoint = function(normal, point) {
    if (normal.isZero()) return null;
    return new Line(normal, -normal.dotProduct(point));
  };


  /**
   * Constructs a line that goes through two given points.
   * Note: the function may return 'null' in case given points are equal.
   * @param {Vector|Point} a The first point the line should go through.
   * @param {Vector|Point} b The second point the line should go through.
   * @returns {Line|null}
   */
  Line.fromPoints = function(a, b) {
    var normal = b.minus(a).ortho();
    return Line.fromNormalAndPoint(normal, a);
  };


  /**
   * Returns the distance from 'this' line to a given point.
   * @param {Vector|Point} point
   * @returns {Number} Distance between 'this' line and the given point.
   */
  Line.prototype.distanceToPoint = function(point) {
    return Math.abs(this.normal.dotProduct(point) + this.c);
  };


  /**
   * Checks whether the point belongs to 'this' line.
   * @param {Vector|Point} point
   * @param {Number|Precision} [precision] Precision for the check.
   * @returns {Boolean} Whether the point belong to 'this' line.
   */
  Line.prototype.containsPoint = function(point, precision) {
    precision = Precision.createForUnit(precision);
    return this.distanceToPoint(point) < precision.UNIT;
  };


  /**
   * Projects a given point onto 'this' line.
   * @param {Vector|Point} point
   * @returns {Point} The projection of the given point onto 'this' line.
   */
  Line.prototype.projectPoint = function(point) {
    var d = this.normal.dotProduct(point) + this.c;
    return point.minus(this.normal.multiply(d));
  };


  /**
   * Finds an intersection point with the given line.
   * Note: may return 'null' in case the lines are parallel or equal.
   * @param {Line} line Line to find intersection with.
   * @returns {Point|null} Intersection point or 'null'.
   */
  Line.prototype.intersectLine = function(line) {
    var d = this.normal.crossProduct(line.normal);
    if (Precision.isZero(d, Precision.DEFAULT.EPSILON)) {
      return null;
    }
    return new Vector((this.normal.y * line.c - this.c * line.normal.y) / d,
                      (this.c * line.normal.x - this.normal.x * line.c) / d);
  };


  /**
   * Checks two lines for equality.
   * To check for equality we compare angles of lines' normals and their
   * distance parameters 'c'.
   * @param {Line} line Line to check for equality.
   * @param {Precision} [precision] Optional Precision object defining
   *     precision constants for angles and distances.
   * @returns {Boolean} Whether two lines are equal.
   */
  Line.prototype.equals = function(line, precision) {
    precision = precision || Precision.DEFAULT;
    var this_angle = this.normal.angle();
    var that_angle = line.normal.angle();
    if (Precision.equal(this_angle, that_angle, precision.RADIAN)) {
      return Precision.equal(this.c, line.c, precision.UNIT);
    }
    that_angle = line.normal.multiply(-1).angle();
    return Precision.equal(this_angle, that_angle, precision.RADIAN) &&
           Precision.equal(this.c, -line.c, precision.UNIT);
  };
  

    /** @exports Segment as geoPolygonUtils.geometry.Segment */
  var Segment =
    /**
     * Creates a segment between two given points.
     * @param {Vector|Point} pointA
     * @param {Vector|Point} pointB
     * @class Represents a segment between two points in 2-dimensional space.
     */
    geoPolygonUtils.geometry.Segment = function(pointA, pointB) {
      /** @type {Point} */
      this.a = pointA;
      /** @type {Point} */
      this.b = pointB;
    };


  /**
   * Returns a line containing 'this' segment or 'null' if the segment is of
   * zero length.
   * @returns {Line|null} A line containing 'this' segment.
   */ 
  Segment.prototype.line = function() {
    return Line.fromPoints(this.a, this.b);
  };


  /**
   * Returns a center of 'this' segment.
   * @returns {Point} The center of 'this' segment.
   */
  Segment.prototype.center = function() {
    return new Vector((this.a.x + this.b.x) * 0.5, (this.a.y + this.b.y) * 0.5);
  };


  /**
   * Returns a distance from 'this' segment to a given point.
   * @param {Vector|Point} point
   * @returns {Number} Distance to the given point.
   */
  Segment.prototype.distanceToPoint = function(point) {
    var u = point.minus(this.a);
    var v = this.b.minus(this.a);
  
    var vLen = v.norm();
    if (Precision.isZero(vLen, Precision.DEFAULT.UNIT)) {
      return u.norm();
    }
    var proj = v.dotProduct(u) / vLen;
    if (proj < 0) {
      return this.a.distanceToPoint(point);
    }
    if (proj > vLen) {
      return this.b.distanceToPoint(point);
    }
    return this.line().distanceToPoint(point);
  };


  /**
   * Checks whether a given point lies on 'this' segment.
   * @param {Vector|Point} point
   * @param {Number|Precision} [precision] Precision for the check.
   * @returns {Boolean} Whether a given point lies on 'this' segment.
   */
  Segment.prototype.containsPoint = function(point, precision) {
    precision = Precision.createForUnit(precision);
    return Precision.isZero(this.distanceToPoint(point), precision.UNIT);
  };


  /**
   * Checks whether a given point is one of the endpoints of 'this' segment.
   * @param {Vector|Point} point
   * @param {Number|Precision} [precision] Precision for the check.
   * @returns {Boolean} Whether a given point is an endpoint of 'this' segment.
   */
  Segment.prototype.isEndpoint = function(point, precision) {
    return this.a.equals(point, precision) || this.b.equals(point, precision);
  };


  /**
   * @returns {Number} Length of 'this' segment.
   */
  Segment.prototype.length = function() {
    return this.a.distanceToPoint(this.b);
  };


  /**
   * Finds an intersection point between two segments.
   * Returns 'null' in case the segments don't intersect or one of the segments
   * has zero length or the segments are parallel (including cases when
   * segments lie on the same line). This also means that there is no way this
   * function can be used to determine whether given segments overlap.
   * @param {Segment} segment Another segment.
   * @returns {Point|null} Intersection point.
   */
  Segment.prototype.intersectSegment = function(segment) {
    var segment_line = segment.line();
    if (segment_line == null) {
      return null;
    }
    var this_line = this.line();
    if (this_line == null) {
      return null;
    }
    var point = segment_line.intersectLine(this_line);
    if (point && segment.containsPoint(point) && this.containsPoint(point)) {
      return point;
    } else {
      return null;
    }
  };
  
})();
